package net.vinant.idp4java.openid4javaImpl;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import net.vinant.idp4java.base.BaseAuthenticationHandler;
import net.vinant.idp4java.base.BaseProtocolImplementation;
import net.vinant.idp4java.base.BaseServiceProxy;
import net.vinant.idp4java.base.IUser;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openid4java.association.AssociationException;
import org.openid4java.message.AuthRequest;
import org.openid4java.message.AuthSuccess;
import org.openid4java.message.DirectError;
import org.openid4java.message.Message;
import org.openid4java.message.MessageException;
import org.openid4java.message.MessageExtension;
import org.openid4java.message.Parameter;
import org.openid4java.message.ParameterList;
import org.openid4java.message.ax.AxMessage;
import org.openid4java.message.ax.FetchRequest;
import org.openid4java.message.ax.FetchResponse;
import org.openid4java.message.sreg.SRegMessage;
import org.openid4java.message.sreg.SRegRequest;
import org.openid4java.message.sreg.SRegResponse;
import org.openid4java.server.ServerException;
import org.openid4java.server.ServerManager;

public class OpenId4JavaProtocolImplementation extends
		BaseProtocolImplementation {

	private enum MODE {associate, checkid_setup, checkid_immediate, check_authentication};
	
	private Log log = LogFactory.getLog(OpenId4JavaProtocolImplementation.class);

	public OpenId4JavaProtocolImplementation(BaseServiceProxy proxy) {
		super(proxy);
	}
	
	@Override
	public String handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		HttpSession session = request.getSession();
		if(isAuthenticationRequest(request)){
			BaseAuthenticationHandler authenticationHandler = ((SimpleServiceProxy)getServiceProxy()).getAuthenticationHandler();
			Object u = request.getParameter("username");
			Object p = request.getParameter("password");
			String username = u!=null?(String)u:null;
			String password = p!=null?(String)p:null;
			IUser authorizedUser = authenticationHandler.authenticate(username, password);
			if(authorizedUser == null){
				//response.sendRedirect("login.jsp");
				request.getRequestDispatcher("/login").forward(request, response);
				return null;
			}else{
				if(session.getAttribute("parameterlist")!=null){
				    ParameterList requestp=(ParameterList) session.getAttribute("parameterlist");
				    String openidrealm=requestp.hasParameter("openid.realm") ? requestp.getParameterValue("openid.realm") : null;
				    String openidreturnto=requestp.hasParameter("openid.return_to") ? requestp.getParameterValue("openid.return_to") : null;
				    String openidclaimedid=requestp.hasParameter("openid.claimed_id") ? requestp.getParameterValue("openid.claimed_id") : null;
				    String openididentity=requestp.hasParameter("openid.identity") ? requestp.getParameterValue("openid.identity") : null;
				    
			        session.setAttribute("openid.realm", openidrealm);
			        session.setAttribute("openid.return_to", openidreturnto);
			        session.setAttribute("openid.claimed_id", openidclaimedid );
			        session.setAttribute("openid.identity", openididentity);			
					session.setAttribute("authenticatedAndApproved", authorizedUser);
				}
			}
		}
		
		ServerManager manager = ((SimpleServiceProxy)getServiceProxy()).getServerManager();
		// extract the parameters from the request
		ParameterList parameterList = null;
				
		if (session.getAttribute("parameterlist")!=null) {
			parameterList = (ParameterList) session.getAttribute("parameterlist"); 
		} else {
			parameterList = new ParameterList(request.getParameterMap());
		}

		String mode = parameterList.hasParameter("openid.mode") ? parameterList.getParameterValue("openid.mode") : null;
		log.debug("openid.mode:" + mode);
		if(StringUtils.isEmpty(mode)){
			//request.getRequestDispatcher("login.jsp").forward(request, response);
			response.sendRedirect("/idp4java/login");
			return null;
		}
			
		switch(MODE.valueOf(mode)){
			case associate: return associate(request, response, parameterList); 
			case checkid_setup: return checkId(request, response, parameterList); 
			case checkid_immediate: return checkId(request, response, parameterList); 
			case check_authentication: return checkAuthentication(request, response, parameterList); 
			default: return unknownError(request, response, parameterList);
		}
	}
	
	private boolean isAuthenticationRequest(HttpServletRequest request) {
		if ("doLogin".equals(request.getParameter("action"))) 
			return true; 
		
		return false;
	}

	private String unknownError(HttpServletRequest request, HttpServletResponse response, ParameterList parameterList) throws IOException {	
		Message messageResponse;
		String responseText;
		// --- error response ---
		// When openid.mode = null or does not match any of the standard modes.
		messageResponse = DirectError.createDirectError("Unknown request");
		responseText = messageResponse.keyValueFormEncoding();
		// return the result to the user
		return directResponse(response,messageResponse.keyValueFormEncoding());
	}

	private String checkAuthentication(HttpServletRequest request, HttpServletResponse response, ParameterList parameterList) throws IOException {
		ServerManager manager = ((SimpleServiceProxy)getServiceProxy()).getServerManager();
		HttpSession session = request.getSession();
		Message messageResponse;
		String responseText;
		
		// --- processing a verification request ---
		messageResponse = manager.verify(parameterList);
		responseText = messageResponse.keyValueFormEncoding();
		return directResponse(response, messageResponse.keyValueFormEncoding());
	}

	private String checkId(HttpServletRequest request, HttpServletResponse response, ParameterList parameterList) throws ServletException, IOException {
		ServerManager manager = ((SimpleServiceProxy)getServiceProxy()).getServerManager();
		HttpSession session = request.getSession();
		Message messageResponse;
		String responseText;
		
		// interact with the user and obtain data needed to continue
		String userSelectedId = null;
		String userSelectedClaimedId = null;
		User authenticatedAndApproved = null;
		String email = "";

		if (session.getAttribute("authenticatedAndApproved") == null) {
			session.setAttribute("parameterlist", parameterList);
			String wwwParams = wwwFormEncoding(parameterList);
			String url = "login.jsp?" + wwwParams;
			response.sendRedirect(url);
			return null;
		} else {
			userSelectedId = (String) session.getAttribute("openid.claimed_id");
			userSelectedClaimedId = (String) session.getAttribute("openid.identity");
			authenticatedAndApproved = (User) session.getAttribute("authenticatedAndApproved");
			// Remove the parameterlist so this provider can accept requests from elsewhere
			session.removeAttribute("parameterlist");
			session.setAttribute("authenticatedAndApproved", null); 
		}

		// --- process an authentication request ---
		AuthRequest authReq = null;
		String opLocalId = null;

		try {
			authReq = AuthRequest.createAuthRequest(parameterList, manager.getRealmVerifier());
			// if the user chose a different claimed_id than the one in request
			if (userSelectedClaimedId != null && userSelectedClaimedId.equals(authReq.getClaimed())) {
				//opLocalId = "http://localhost:8081/idp4java/idp";//lookupLocalId(userSelectedClaimedId);
			}
		} catch (MessageException e) {
			e.printStackTrace();
			throw new ServletException(e);
		}

		// --- process an authentication request ---
		//messageResponse = manager.authResponse(parameterList, userSelectedId, userSelectedClaimedId, true);
		messageResponse = manager.authResponse(parameterList, opLocalId, opLocalId, true);
		

		if (messageResponse instanceof DirectError)
			return directResponse(response, messageResponse.keyValueFormEncoding());
		else {

            try {
				if (authReq.hasExtension(AxMessage.OPENID_NS_AX))
				{
				    MessageExtension ext = authReq.getExtension(AxMessage.OPENID_NS_AX);
				    if (ext instanceof FetchRequest)
				    {
				        FetchRequest fetchReq = (FetchRequest) ext;
				        Map required = fetchReq.getAttributes(true);
				        Map optional = fetchReq.getAttributes(false);
				        if (required.containsKey("email"))
				        {
				            Map userDataExt = new HashMap();
				            userDataExt.put("email", "someone@someplace.com");
				            FetchResponse fetchResp = FetchResponse.createFetchResponse(fetchReq, userDataExt);
				            // (alternatively) manually add attribute values
				            //fetchResp.addAttribute("email", "http://schema.openid.net/contact/email", email);
				            messageResponse.addExtension(fetchResp);
				        }
				    }
				    else //if (ext instanceof StoreRequest)
				    {
				        throw new UnsupportedOperationException("TODO");
				    }
				}
				if (authReq.hasExtension(SRegMessage.OPENID_NS_SREG))
				{
				    MessageExtension ext = authReq.getExtension(SRegMessage.OPENID_NS_SREG);
				    if (ext instanceof SRegRequest)
				    {
				        SRegRequest sregReq = (SRegRequest) ext;
				        List required = sregReq.getAttributes(true);
				        List optional = sregReq.getAttributes(false);
				        if (required.contains("email"))
				        {
				            // data released by the user
				            Map userDataSReg = new HashMap();
				            userDataSReg.put("email", "user@example.com");

				            SRegResponse sregResp = SRegResponse.createSRegResponse(sregReq, userDataSReg);
				            // (alternatively) manually add attribute values
				            //sregResp.addAttribute("email", email);
				            messageResponse.addExtension(sregResp);
				        }
				    }
				    else
				    {
				        throw new UnsupportedOperationException("TODO");
				    }
				}

				// Sign the auth success message.
				// This is required as AuthSuccess.buildSignedList has a `todo' tag now.
				manager.sign((AuthSuccess) messageResponse);
			} catch (MessageException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (ServerException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (AssociationException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

            // caller will need to decide which of the following to use:

            // option1: GET HTTP-redirect to the return_to URL
            
    		session.setAttribute("SUCCESS", Boolean.TRUE);
    		response.sendRedirect(messageResponse.getDestinationUrl(true));

            // option2: HTML FORM Redirection
            //RequestDispatcher dispatcher =
            //        getServletContext().getRequestDispatcher("formredirection.jsp");
            //httpReq.setAttribute("prameterMap", response.getParameterMap());
            //httpReq.setAttribute("destinationUrl", response.getDestinationUrl(false));
            //dispatcher.forward(request, response);
            //return null;

			return null;
		}
		 
	}

	private String associate(HttpServletRequest request, HttpServletResponse response, ParameterList parameterList) throws IOException {
		ServerManager manager = ((SimpleServiceProxy)getServiceProxy()).getServerManager();
		HttpSession session = request.getSession();
		Message messageResponse;
		String responseText;
		// --- process an association parameterList ---
		messageResponse = manager.associationResponse(parameterList);
		responseText = messageResponse.keyValueFormEncoding();
		return directResponse(response, messageResponse.keyValueFormEncoding());
		
	}

	private String directResponse(HttpServletResponse response, String messageResponse)
            throws IOException
    {
        ServletOutputStream os = response.getOutputStream();
        os.write(messageResponse.getBytes());
        os.close();

        return null;
    }
	
	
    private String wwwFormEncoding(ParameterList parameterList)
    {
        StringBuffer allParams = new StringBuffer("");

        List parameters = parameterList.getParameters();
        Iterator iterator = parameters.iterator();
        while (iterator.hasNext())
        {
            Parameter parameter = (Parameter) iterator.next();

            // All of the keys in the request message MUST be prefixed with "openid."
            if ( ! parameter.getKey().startsWith("openid."))
                allParams.append("openid.");

            try
            {
                allParams.append(URLEncoder.encode(parameter.getKey(), "UTF-8"));
                allParams.append('=');
                allParams.append(URLEncoder.encode(parameter.getValue(), "UTF-8"));
                allParams.append('&');
            }
            catch (UnsupportedEncodingException e)
            {
                return null;
            }
        }

        // remove the trailing '&'
        if (allParams.length() > 0)
            allParams.deleteCharAt(allParams.length() -1);

        return allParams.toString();
    }

}
